﻿using CSRedis;
using Polly;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace SX.Core.Cache
{
    /// <summary>
    /// redis分布式锁
    /// </summary>
    public class RedisLock : IDistributedLock
    {
        /// <summary>
        /// 加锁 说明:redis集群时候如果lua脚本有多个key那么执行lua脚本会报错，因为集群中会根据key进行CRC16算法分配到不同的哈希槽，可以在key加上{}保证分配到同一槽位
        /// </summary>
        /// <param name="LockKey">锁key</param>
        /// <param name="LockExpirySeconds">过期时间s</param>
        /// <param name="selfMark">锁标识</param>
        /// <param name="retryAttemptMillseconds">重试间隔毫秒</param>
        /// <param name="retryTimes">重试次数</param>
        /// <returns></returns>
        public bool Lock(string LockKey, int LockExpirySeconds,out string selfMark, int retryAttemptMillseconds = 50, int retryTimes = 5)
        {
            selfMark = GuidHelper.NewSequentialGuid();//锁的标识
            //var cacheKey = "{sxm}:Lock:"+ LockKey;
            var cacheKey = "Lock:" + LockKey;
            using (var redisClient = new CSRedis.CSRedisClient(ConfigHelper.Get("RedisConnStr")))
            {
                string script = $"if redis.call('SETNX',KEYS[1],ARGV[1])==1 then redis.call('PEXPIRE',KEYS[1],{LockExpirySeconds*1000}) return 1 else return 0 end";
                do
                {
                    var executeLuaResult = redisClient.Eval(script, cacheKey, selfMark).To_Int() == 1;
                    if (!executeLuaResult)
                    {
                        retryTimes--;
                        if (retryTimes < 0)
                        {
                            return false;
                        }
                        if (retryAttemptMillseconds > 0)
                        {
                            //Console.WriteLine($"Wait Lock {LockKey} to {retryAttemptMillseconds} millseconds");
                            //获取锁失败则进行锁等待
                            System.Threading.Thread.Sleep(retryAttemptMillseconds);
                        }
                    }
                    else
                    {
                        return true;
                    }
                }
                while (retryTimes > 0);
                return false;
            }
           
        }
        /// <summary>
        /// 释放锁
        /// </summary>
        /// <param name="LockName"></param>
        /// <param name="LockToken"></param>
        public void UnLock(string LockKey, string selfMark)
        {
            using (var redisClient = new CSRedis.CSRedisClient(ConfigHelper.Get("RedisConnStr")))
            {
                var cacheKey = "Lock:" + LockKey;
                //var cacheKey = "{sxm}:Lock:" + LockKey;
                var script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                var polly = Policy.Handle<Exception>()
                   .WaitAndRetry(10, retryAttempt => TimeSpan.FromMilliseconds(Math.Pow(2, retryAttempt)), (exception, timespan, retryCount, context) =>
                   {
                       Console.WriteLine($"执行异常,重试次数：{retryCount},【异常来自：{exception.GetType().Name}】.");
                   });
                polly.Execute(() =>
                {
                    redisClient.Eval(script, cacheKey, selfMark);
                });
            }
        }

        public  bool Lock2(string key, out string selfMark, int lockExpirySeconds = 10, long waitLockMilliseconds =long.MaxValue)
        {
            DateTime begin = DateTime.Now;
            selfMark = Guid.NewGuid().ToString("N");//自己标记,释放锁时会用到,自己加的锁除非过期否则只能自己打开
            using (CSRedisClient redisClient = new CSRedis.CSRedisClient(ConfigHelper.Get("RedisConnStr")))
            {
                string lockKey = "Lock:" + key;
                while (true)
                {
                    string script = string.Format("if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 then redis.call('PEXPIRE',KEYS[1],{0}) return 1 else return 0 end", lockExpirySeconds*1000);
                    //循环获取取锁
                    if (redisClient.Eval(script,  lockKey ,  selfMark).To_Int()==1)
                    {
                        return true;
                    }

                    //不等待锁则返回
                    if (waitLockMilliseconds == 0)
                    {
                        break;
                    }

                    //超过等待时间，则不再等待
                    if ((DateTime.Now - begin).TotalMilliseconds >= waitLockMilliseconds)
                    {
                        break;
                    }
                    Thread.Sleep(50);
                }

                return false;
            }
        }
    }
}
